#include "declareVariables.h"
#include "InputArray.h"
#include "fastCalculate.h"
#include "CPUTime.h"
#include <iostream>
#include <fstream>
#include "assert.h"

using namespace std;


/////////////////////////////////////////////////////////////////////////////////////
// debugInputArray tests the InputArray.UpdateArray() function
////////////////////////////////////////////////////////////////////////////////////

void debugInputArray(){

	cout << "In debug InputArray" << endl;
	double * sortedArray, *sortedTableArray;
	sortedArray = (double *) malloc (sizeof(double) * 500);
	sortedTableArray = (double *) malloc (sizeof(double) * 500);
	InputArray InArray = InputArray(500);
    fstream myfile("gbpusd.txt", ios::in);
    InArray.InitiateArray(myfile);
//    InArray.PrintElements(false);
    for (int i = 0; i< 10000; i ++){
    	int arrayLength=0;
    	InArray.UpdateArray(myfile);
    	for (int j = 0; j < InArray.tableLength; j++)
    		arrayLength += InArray.TablePointer[j].NumberofElements;
    	if (arrayLength != 500){
    		cout << "Table Generated Array Length is not 500 at index " << i << endl;
    	}
    	//////////////////////////////////////////////////////////////////
    	// Build Sorted Array
    	//////////////////////////////////////////////////////////////////
    	for (int j= 0; j < windowSize; j++)
    		sortedArray[j] = InArray.CurrentArrayPtr[j];
    	double tmp;
    	for(int j = 0; j < windowSize-1 ;j++){
    		for (int k = j + 1;k < windowSize;k++){
    			if (sortedArray[j] > sortedArray[k]){
    				tmp  = sortedArray[j];
    				sortedArray[j] = sortedArray[k];
    				sortedArray[k] = tmp;
    			}
    		}
    	}

     	//////////////////////////////////////////////////////////////////
		// Build Sorted Table Array
		//////////////////////////////////////////////////////////////////
    	int tableArrayIndex = 0;
    	for (int j = 0; j < InArray.tableLength; j++){
    		for (int k = 0; k< InArray.TablePointer[j].NumberofElements; k++)
    			sortedTableArray[tableArrayIndex++] = InArray.TablePointer[j].ValueofElement;
    	}

    	if (tableArrayIndex != 500)
    		cout << "Error in building Table Array";

    	///////////////////////////////////////////////////////////////////
    	// Check the Arrays vs each other
    	//////////////////////////////////////////////////////////////////
    	for (int j= 0; j < windowSize; j++){
    		if (sortedArray[j] != sortedTableArray[j]){
    			cout << "Error, Arrays do not match" << endl;
    		}
    	}
    }

    InArray.PrintElements(false);
    free (sortedArray);
    free (sortedTableArray);
    myfile.close();
    return;
}

/////////////////////////////////////////////////////////////////////////////////////
// testCorrectness tests the results of the two versions of CUDAfastAEPD_DistWrapper
// Reminder: the first version uses an array, the 2nd version uses a table.
////////////////////////////////////////////////////////////////////////////////////

void testCorrectness(){

	///////////////////////////////////////////////////////////////////////////////
	// alpha and log-gamma(1/alpha) values are constant, declare and copy them here
	///////////////////////////////////////////////////////////////////////////////
	int lengthDevAlpha = 80;
	double *alpha, *logGammaofAlpha, *deviationMatrix;
	// device variables.
	double *dev_alpha, *dev_lnGammaofAlpha, * dev_deviationMatrix;
	alpha = (double *) malloc (sizeAlpha*sizeof(double));
	logGammaofAlpha = (double *) malloc (sizeAlpha*sizeof(double));

	cudaMalloc( (void **)&dev_alpha, sizeof(double) * sizeAlpha );
	cudaMalloc( (void **)&dev_lnGammaofAlpha, sizeof(double) * sizeAlpha );

	cudaMemcpy( dev_alpha, alpha, sizeof(double) * sizeAlpha, cudaMemcpyHostToDevice );
	cudaMemcpy( dev_lnGammaofAlpha, logGammaofAlpha, sizeof(double)*sizeAlpha, cudaMemcpyHostToDevice );
	deviationMatrix = (double *) malloc(sizeAlpha*numDIV*sizeof(double));
	cudaMalloc( (void **)&dev_deviationMatrix, sizeof(double) * sizeAlpha*numDIV);


	CudaDeclareVariablesWrapper(alpha, logGammaofAlpha, dev_alpha, dev_lnGammaofAlpha, deviationMatrix, dev_deviationMatrix, lengthDevAlpha);

	InputArray InArray = InputArray(500);
	fstream myfile("gbpusd.txt", ios::in);
	InArray.InitiateArray(myfile);
    double *param;
	param = (double *) malloc (4 * sizeof(double));

//	for (int i = 0; i < 45; i++){
//    	InArray.UpdateArray(myfile);
//	}
//	InArray.PrintElements(false);
//	CUDAfastAEPD_DistWrapper (InArray.CurrentArrayPtr, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//	for (int i = 0; i < 4; i++){
//		cout << "Param[" << i << "] = " << param[i]<<"        ";
//	}
//	cout << endl;
//
//	CUDAfastAEPD_DistWrapper (InArray.tableLength, InArray.TablePointer, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//	cout<< "Parameters from Table Cuda AEPD_Fit" << endl;
//
//	for (int i = 0; i < 4; i++){
//	cout << "Param[" << i << "] = " << param[i]<<"        ";
//	}
//	cout << endl;

	int j = 0;
	while (InArray.tableLength < 8){
//	while (j < 10){
    	InArray.UpdateArray(myfile);

		CUDAfastAEPD_DistWrapper (InArray.CurrentArrayPtr, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//		cout<< "New Parameters from fast sum Cuda AEPD_Fit" << endl;
		cout << "iteration = " << j << endl;
		for (int i = 0; i < 4; i++){
			cout << "Param[" << i << "] = " << param[i]<<"        ";
		}
		cout << endl;

		CUDAfastAEPD_DistWrapper (InArray.tableLength, InArray.TablePointer, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//		cout<< "Parameters from Table Cuda AEPD_Fit" << endl;

		for (int i = 0; i < 4; i++){
			cout << "Param[" << i << "] = " << param[i]<<"        ";
		}
		cout << endl;
		j++;

		localAEPD_Fit(InArray.CurrentArrayPtr, 500, param);
		for (int i = 0; i < 4; i++){
			cout << "Param[" << i << "] = " << param[i]<<"        ";
		}
		cout << endl;
    }

    free (param);
	freeMemories(alpha, logGammaofAlpha, dev_alpha, dev_lnGammaofAlpha, deviationMatrix, dev_deviationMatrix);
	myfile.close();
	return;
}

/////////////////////////////////////////////////////////////////////////////////////
// testStructureCorrectness tests the results C++ data structure version of AEPD fit
// no GPUs involved.
////////////////////////////////////////////////////////////////////////////////////

void testStructureCorrectness(){

	InputArray InArray = InputArray(500);
	fstream myfile("gbpusd.txt", ios::in);
	InArray.InitiateArray(myfile);
    double *param, *param1;
	param = (double *) malloc (4 * sizeof(double));
	param1 = (double *) malloc (4 * sizeof(double));
	bool testPassed = true;

	for (int m = 0 ; m < 1000; m++){
    	InArray.UpdateArray(myfile);

		localAEPD_Fit(InArray.CurrentArrayPtr, 500, param);

		structureAEPD_Fit (InArray.tableLength, InArray.TablePointer, windowSize, param1);

	    for (int i = 0; i < 4; i++){
	    	cout << "Param[" << i << "] = " << param[i] << "          ";
			cout << "Param[" << i << "] = " << param1[i] << endl;
	    }
	    cout << endl;
	    for (int i = 0; i < 4; i++){
	    	if (param1[i] != param[i]){
	    		testPassed = false;
	    		cout << " m = " << m << endl;
	    		break;
	    	}
	    }

    }

	cout << "Test Passed : " << testPassed << endl;
    free (param);
    free (param1);
	myfile.close();
	return;
}




int main(void){
//      debugInputArray();
//      testCorrectness();
//		testStructureCorrectness();
	  ///////////////////////////////////////////////////////////////////////////////
	  // alpha and log-gamma(1/alpha) values are constant, declare and copy them here
	  ///////////////////////////////////////////////////////////////////////////////
	  int lengthDevAlpha = 80;
	  double *alpha, *logGammaofAlpha, *deviationMatrix;
	  // device variables.
	  double *dev_alpha, *dev_lnGammaofAlpha, * dev_deviationMatrix;
	  alpha = (double *) malloc (sizeAlpha*sizeof(double));
	  logGammaofAlpha = (double *) malloc (sizeAlpha*sizeof(double));

	  cudaMalloc( (void **)&dev_alpha, sizeof(double) * sizeAlpha );
	  cudaMalloc( (void **)&dev_lnGammaofAlpha, sizeof(double) * sizeAlpha );

	  cudaMemcpy( dev_alpha, alpha, sizeof(double) * sizeAlpha, cudaMemcpyHostToDevice );
	  cudaMemcpy( dev_lnGammaofAlpha, logGammaofAlpha, sizeof(double)*sizeAlpha, cudaMemcpyHostToDevice );
	  deviationMatrix = (double *) malloc(sizeAlpha*numDIV*sizeof(double));
	  cudaMalloc( (void **)&dev_deviationMatrix, sizeof(double) * sizeAlpha*numDIV);


	  CudaDeclareVariablesWrapper(alpha, logGammaofAlpha, dev_alpha, dev_lnGammaofAlpha, deviationMatrix, dev_deviationMatrix, lengthDevAlpha);
	  ///////////////////////////////////////////////////////////////////////////////
	  // Debugging mem copy
	  //////////////////////////////////////////////////////////////////////////////
/*	  double *debug1;
		debug1= (double *) malloc (sizeAlpha*sizeof(double));
		cudaMemcpy(debug1 , dev_alpha, sizeof(double) * sizeAlpha , cudaMemcpyDeviceToHost );

		for (int i = 0; i < sizeAlpha; i++){
			cout << "alpha[" << i << "] = " << debug1[i] << endl;
		}

		cudaMemcpy(debug1 , dev_lnGammaofAlpha, sizeof(double) * sizeAlpha , cudaMemcpyDeviceToHost );
		for (int i = 0; i < sizeAlpha; i++){
			cout << "log gamma alpha[" << i << "] = " << debug1[i] << endl;
		}


	  free(debug1);
*/
	  ///////////////////////////////////////////////////////////////////////////////
	  // End debugging mem copy
	  //////////////////////////////////////////////////////////////////////////////

	  ///////////////////////////////////////////////////////////////////////////////
	  // Processing
	  //////////////////////////////////////////////////////////////////////////////

	  InputArray InArray = InputArray(500);
      fstream myfile("gbpusd.txt", ios::in);
      InArray.InitiateArray(myfile);
      InArray.UpdateArray(myfile);
//      InArray.PrintElements(false);


      double *param;
	  param = (double *) malloc (4 * sizeof(double));

//      CUDAfastAEPD_DistWrapper (InArray.CurrentArrayPtr, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//	  cout<< "New Parameters from fast sum Cuda AEPD_Fit" << endl;
//      for (int i = 0; i < 4; i++){
//		cout << "Param[" << i << "] = " << param[i]<<endl;
//      }
//
//      CUDAfastAEPD_DistWrapper (InArray.tableLength, InArray.TablePointer, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//	  cout<< "Parameters from Table Cuda AEPD_Fit" << endl;
//      for (int i = 0; i < 4; i++){
//		cout << "Param[" << i << "] = " << param[i]<<endl;
//      }

	  localAEPD_Fit(InArray.CurrentArrayPtr, windowSize, param);
	  cout<< "Parameters from local AEPD Fit" << endl;
      for (int i = 0; i < 4; i++){
		cout << "Param[" << i << "] = " << param[i]<<endl;
      }


      structureAEPD_Fit (InArray.tableLength, InArray.TablePointer, windowSize, param);
	  cout<< "Parameters from structure AEPD Fit" << endl;
      for (int i = 0; i < 4; i++){
		cout << "Param[" << i << "] = " << param[i]<<endl;
      }



      //////////////////////////////////////////////////////////////////////////////////////////////
      // Check time to process using object oriented version
      //////////////////////////////////////////////////////////////////////////////////////////////

  	TimeVal startTime, endTime;
  	long totalTime;

//  	assert(GetTime(startTime)==0);
//	for (int i = 0; i < 100; i++){
//	      CUDAfastAEPD_DistWrapper (InArray.CurrentArrayPtr, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//	}
//
//	assert(GetTime(endTime)==0);
//	totalTime=GetTimeDifference(startTime, endTime);
//	cout << "Total time = " << totalTime << " microseconds." << endl;
//	cout << "Average time of data array Cuda : " << totalTime/100 << " microseconds." << endl;
//
//	// Cuda performance, constant memory, local C++ clock
//	assert(GetTime(startTime)==0);
//	for (int i = 0; i < 100; i++){
//	      CUDAfastAEPD_DistWrapper (InArray.tableLength, InArray.TablePointer, param, dev_alpha, dev_lnGammaofAlpha, dev_deviationMatrix);
//	}
//
//	assert(GetTime(endTime)==0);
//	totalTime=GetTimeDifference(startTime, endTime);
//	cout << "Total time = " << totalTime << " microseconds." << endl;
//	cout << "Average time of table CUDA : " << totalTime/100 << " microseconds." << endl;

  	assert(GetTime(startTime)==0);
	for (int i = 0; i < 100; i++){
	      structureAEPD_Fit (InArray.tableLength, InArray.TablePointer, windowSize, param);
	}

	assert(GetTime(endTime)==0);
	totalTime=GetTimeDifference(startTime, endTime);
	cout << "Total time = " << totalTime << " microseconds." << endl;
	cout << "Average time of single run on same data : " << totalTime/100 << " microseconds." << endl;

	// Cuda performance, constant memory, local C++ clock
	assert(GetTime(startTime)==0);
	for (int i = 0; i < 100; i++){
	      InArray.UpdateArray(myfile);
	      structureAEPD_Fit (InArray.tableLength, InArray.TablePointer, windowSize, param);
	}

	assert(GetTime(endTime)==0);
	totalTime=GetTimeDifference(startTime, endTime);
	cout << "Total time = " << totalTime << " microseconds." << endl;
	cout << "Average time of single run on different data : " << totalTime/100 << " microseconds." << endl;

	  //////////////////////////////////////////////////////////////////////////////
	  // End processing, clear global variables
	  //////////////////////////////////////////////////////////////////////////////
//      freeMemories(alpha, logGammaofAlpha, dev_alpha, dev_lnGammaofAlpha, deviationMatrix, dev_deviationMatrix);
//      myfile.close();
	  return 0;

}
